package com.snap.vseries.analyze;

import android.util.Log;
import android.util.SparseArray;

import com.orhanobut.logger.Logger;
import com.snap.vseries.AppConfig;
import com.snap.vseries.Constants;
import com.snap.vseries.bean.AnalysisProtocol;
import com.snap.vseries.bean.ChannelBean;
import com.snap.vseries.bean.ExpInfo;
import com.snap.vseries.bean.HoleInfo;
import com.snap.vseries.bean.LightBean;
import com.snap.vseries.manager.FileManager;

import org.apache.commons.math3.fitting.PolynomialCurveFitter;
import org.apache.commons.math3.fitting.WeightedObservedPoints;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.LUDecomposition;
import org.apache.commons.math3.linear.RealMatrix;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class AnalyzeManager {

    private static final String TAG = "AnalyzeManager";

    public static void doAnalyze(ExpInfo experimentInfo) {
        long t1 = System.currentTimeMillis();
        experimentInfo.setAnalyzed(false);

        // 1.预处理
        fill(experimentInfo);

        // 2.ct分析
        List<AnalysisProtocol> protocols = experimentInfo.getProtocols();
        AlgoParams algoParams = experimentInfo.getAlgoParams();
        if (algoParams.getMainAlgo().equals("default")) {
            defAlgo(experimentInfo, protocols, algoParams);
        } else if (algoParams.getMainAlgo().equals("origin")) {
            MoleAlgo.execute(experimentInfo.getHoleInfo().getChannelData(), experimentInfo.getProtocols());
        } else {
            defAlgo(experimentInfo, protocols, algoParams);
        }

        // 3.熔解分析
        meltAnalysis(experimentInfo);

        experimentInfo.setAnalyzed(true);
        FileManager.getInstance().saveExpData(experimentInfo);
        Log.e(TAG, "分析完成: " + (System.currentTimeMillis() - t1));
    }

    private static void defAlgo(ExpInfo experimentInfo, List<AnalysisProtocol> protocols, AlgoParams algoParams) {
        List<List<ChannelBean>> list = new ArrayList<>();
        for (int i = 0; i < AppConfig.TOTAL_CHANNEL_COUNT; i++) {
            list.add(new ArrayList<>());
        }
        for (int i = 0; i < experimentInfo.getHoleInfo().getChannelData().size(); i++) {
            ChannelBean bean = experimentInfo.getHoleInfo().getChannelData().valueAt(i);
            list.get(bean.getChannelIndex()).add(bean);
        }
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).size() == 0) continue;
            // 2.1 基线分析
            Logger.i("开始分析%d通道的基线", i + 1);
            baseline(list.get(i), protocols.get(i), algoParams);

            // 2.2 相对分析
            Logger.i("开始分析%d通道的相对", i + 1);
            relative(list.get(i), protocols.get(i), algoParams);

            // 2.3 阈值分析
            Logger.i("开始分析%d通道的阈值", i + 1);
            threshold(list.get(i), protocols.get(i), algoParams);

            // 2.4 ct分析
            Logger.i("开始分析%d通道的ct", i + 1);
            ctValue(list.get(i), protocols.get(i), algoParams);
        }
    }

    private static void baseline(List<ChannelBean> channelBeans, AnalysisProtocol analysisProtocol, AlgoParams algoParams) {
        if (!analysisProtocol.isAutoBaseline()) {//手动基线
            BaselineManual.doAnalyze(channelBeans, analysisProtocol.getBaselineStart(), analysisProtocol.getBaselineStop());
        } else if (algoParams.getBaselineMethod().matches("ma")) {//完全的ma基线
            BaselineMa.doAnalyze(channelBeans, analysisProtocol, algoParams);
        } else if (algoParams.getBaselineMethod().matches("ab")) {//ma调整的
           BaselineAb.doAnalyze(channelBeans, analysisProtocol, algoParams);
        } else {
            //do nothing
        }
    }

    private static void relative(List<ChannelBean> channelBeans, AnalysisProtocol analysisProtocol, AlgoParams algoParams) {
        if (algoParams.getRelativeMethod().matches("normal")) {
            RelativeNormal.doAnalyze(channelBeans, algoParams);
        } else {
            //do nothing
        }
    }

    private static void threshold(List<ChannelBean> channelBeans, AnalysisProtocol analysisProtocol, AlgoParams algoParams) {
        if (!analysisProtocol.isAutoThreshold()) {//手动阈值
            ThresholdManual.doAnalyze(channelBeans, analysisProtocol.getThreshold());
        } else if (algoParams.getThresholdMethod().matches("sd10")) {//标准差10倍
            ThresholdStd10.doAnalyze(channelBeans, algoParams);
        } else {
            //do nothing
        }
    }

    private static void ctValue(List<ChannelBean> channelBeans, AnalysisProtocol analysisProtocol, AlgoParams algoParams) {
        if (algoParams.getCtFindMethod().matches("linear")) {
            CtValueLinear.doAnalyze(channelBeans, algoParams);
        } else if (algoParams.getCtFindMethod().matches("ma")) {
            CtValueLinear.doAnalyze(channelBeans, algoParams);
        } else {
            //do nothing
        }
    }

    /**
     * 0.校验 ：分析的先决条件
     */
//    private static void precondition(ExperimentInfo experimentInfo, double[] crossTalk) throws AnalyzeFailedException {
//        if (experimentInfo == null) {
//            throw new AnalyzeFailedException(AnalyzeFailedException.ERROR_CODE_EMPTY_EXP);
//        }
//
//        int holeCount = experimentInfo.getHoleCount();
//
//        // 排除一些不能进行分析的情况
//        if (holeCount != AppConfig.TOTAL_HOLE_COUNT) {
//            throw new AnalyzeFailedException(AnalyzeFailedException.ERROR_CODE_DATA_NOT_MATCH);
//        }
//        if (crossTalk.length != AppConfig.TOTAL_CHANNEL_COUNT * AppConfig.TOTAL_CHANNEL_COUNT) {
//            throw new AnalyzeFailedException(AnalyzeFailedException.ERROR_CODE_DATA_NOT_MATCH);
//        }
//
//        TemplateInfo templateInfo = experimentInfo.getTemplateInfo();
//        if (templateInfo == null) {
//            throw new AnalyzeFailedException(AnalyzeFailedException.ERROR_CODE_NO_TEMPLATE);
//        }
//        if (templateInfo.getSampleList() == null || templateInfo.getSampleList().isEmpty()) {
//            throw new AnalyzeFailedException(AnalyzeFailedException.ERROR_CODE_NO_SAMPLE);
//        }
//
//        SparseArray<List<LightBean>> lightData = experimentInfo.getLightData();
//        if (lightData == null || lightData.size() == 0) {
//            throw new AnalyzeFailedException(AnalyzeFailedException.ERROR_CODE_LIGHT_DATA_EMPTY);
//        }
//        boolean all0 = true;
//        for (int i = 0; i < lightData.size(); i++) {
//            List<LightBean> value = lightData.valueAt(i);
//            if (value.size() > 0) {
//                all0 = false;
//                break;
//            }
//        }
//        if (all0) {
//            throw new AnalyzeFailedException(AnalyzeFailedException.ERROR_CODE_LIGHT_DATA_EMPTY);
//        }
//    }

    /**
     * 1.数据的预处理
     */
    private static void fill(ExpInfo experimentInfo) {
        double[] crossTalk = experimentInfo.getCrosstalk();
        if (crossTalk == null) {
            crossTalk = new double[]{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
        }
        double b[][] = new double[4][4];
        b[0][0] = crossTalk[0];
        b[0][1] = crossTalk[1];
        b[0][2] = crossTalk[2];
        b[0][3] = crossTalk[3];

        b[1][0] = crossTalk[4];
        b[1][1] = crossTalk[5];
        b[1][2] = crossTalk[6];
        b[1][3] = crossTalk[7];

        b[2][0] = crossTalk[8];
        b[2][1] = crossTalk[9];
        b[2][2] = crossTalk[10];
        b[2][3] = crossTalk[11];

        b[3][0] = crossTalk[12];
        b[3][1] = crossTalk[13];
        b[3][2] = crossTalk[14];
        b[3][3] = crossTalk[15];
        RealMatrix matrix = new Array2DRowRealMatrix(b);
        RealMatrix inverseMatrix = null;

        try {
            //求逆矩阵
            inverseMatrix = new LUDecomposition(matrix).getSolver().getInverse();
        } catch (Exception e) {
            e.printStackTrace();
        }

        //experimentInfo.getLightData();有512条列表（List<LightBean>），0~127,1000~1127,2000~2127,300~3127(128*4=512)
        SparseArray<List<LightBean>> origin = experimentInfo.getLightData();
        HoleInfo holeInfo = experimentInfo.getHoleInfo();
        holeInfo.setChannelData(new SparseArray<>());

        for (int i = 0; i < AppConfig.TOTAL_HOLE_COUNT; i++) {

            List<LightBean> famList = origin.get(i + AppConfig.TOTAL_HOLE_COUNT * 0);
            List<LightBean> vicList = origin.get(i + AppConfig.TOTAL_HOLE_COUNT * 1);
            List<LightBean> roxList = origin.get(i + AppConfig.TOTAL_HOLE_COUNT * 2);
            List<LightBean> cy5List = origin.get(i + AppConfig.TOTAL_HOLE_COUNT * 3);

            int size = experimentInfo.getRunCycle();
            size = Math.min(size, famList.size());
            size = Math.min(size, vicList.size());
            size = Math.min(size, roxList.size());
            size = Math.min(size, cy5List.size());

            float[] famData = new float[size];
            float[] vicData = new float[size];
            float[] roxData = new float[size];
            float[] cy5Data = new float[size];

            for (int j = 0; j < size; j++) {
                int fam = famList.get(j).getIntensity();
                int vic = vicList.get(j).getIntensity();
                int rox = roxList.get(j).getIntensity();
                int cy5 = cy5List.get(j).getIntensity();

                if (inverseMatrix == null) {
                    famData[j] = fam;
                    vicData[j] = vic;
                    roxData[j] = rox;
                    cy5Data[j] = cy5;
                } else {
                    famData[j] = (float) (fam * inverseMatrix.getRow(0)[0] + vic * inverseMatrix.getRow(0)[1] + rox * inverseMatrix.getRow(0)[2] + cy5 * inverseMatrix.getRow(0)[3]);
                    vicData[j] = (float) (fam * inverseMatrix.getRow(1)[0] + vic * inverseMatrix.getRow(1)[1] + rox * inverseMatrix.getRow(1)[2] + cy5 * inverseMatrix.getRow(1)[3]);
                    roxData[j] = (float) (fam * inverseMatrix.getRow(2)[0] + vic * inverseMatrix.getRow(2)[1] + rox * inverseMatrix.getRow(2)[2] + cy5 * inverseMatrix.getRow(2)[3]);
                    cy5Data[j] = (float) (fam * inverseMatrix.getRow(3)[0] + vic * inverseMatrix.getRow(3)[1] + rox * inverseMatrix.getRow(3)[2] + cy5 * inverseMatrix.getRow(3)[3]);
                }
            }

            for (int j = 0; j < 4; j++) {
                if ((holeInfo.getChannelJoinFlag() >> j & 0x01) == 0x01) {
                    ChannelBean channelBean = new ChannelBean();
                    switch (j) {
                        case 0:
                            channelBean.setOrigin(SmoothUtil.quadraticSmooth5(famData));
                            break;
                        case 1:
                            channelBean.setOrigin(SmoothUtil.quadraticSmooth5(vicData));
                            break;
                        case 2:
                            channelBean.setOrigin(SmoothUtil.quadraticSmooth5(roxData));
                            break;
                        case 3:
                            channelBean.setOrigin(SmoothUtil.quadraticSmooth5(cy5Data));
                            break;
                        default:
                    }
                    channelBean.setIndex(j * AppConfig.TOTAL_HOLE_COUNT + i);
                    channelBean.setHoleIndex(i);
                    channelBean.setChannelIndex(j);
                    experimentInfo.getHoleInfo().getChannelData().put(channelBean.getIndex(), channelBean);
                }
            }
        }
    }

    /**
     * 3. 熔解分析
     */
    private static void meltAnalysis(ExpInfo experimentInfo) {
        boolean isOpen = experimentInfo.getExperimentType() == Constants.EXPERIMENT_TYPE_MELT && experimentInfo.getProgramMeltStage() != null;
        if (!isOpen) return;

        //每隔多少度采一个点(0.2)
        float lightAndFrequency = AppConfig.PER_LIGHT_TIME * experimentInfo.getProgramMeltStage().getLevel() / 100f;
        float startTemp = experimentInfo.getProgramMeltStage().getStartTemp();
        float endTemp = experimentInfo.getProgramMeltStage().getEndTemp();
        boolean tempIncrease = startTemp < endTemp;

        for (int i = 0; i < AppConfig.TOTAL_HOLE_COUNT; i++) {
            ChannelBean bean = experimentInfo.getHoleInfo().getChannelData().get(i);
            if (bean == null) continue;
            List<LightBean> meltList = experimentInfo.getLightData().get(i + 3000);
            float[] meltData = new float[meltList.size()];
            for (int j = 0; j < meltList.size(); j++) {
                meltData[j] = meltList.get(j).getIntensity();
            }
            bean.setMeltOrigin(meltData);

            if (bean.getMeltOrigin() != null && bean.getMeltOrigin().length > 10) {
                curveMeltFit(bean, tempIncrease, startTemp, lightAndFrequency);
            }
        }
    }

    private static void curveMeltFit(ChannelBean bean, boolean tempIncrease, float startTemp, float lightAndFrequency) {
        float[] copy = bean.getMeltOrigin().clone();

        for (int j = 0; j < 50; j++) {
            for (int i = 1; i < copy.length - 1; i++) {
                float s = copy[i];
                float s1 = copy[i + 1];
                float s1Minus = copy[i - 1];
                if ((s > s1 && s > s1Minus) || (s < s1 && s < s1Minus)) {
                    copy[i] = (s1 + s1Minus) / 2;
                }
            }
        }

        float[] reverseD = new float[copy.length];
        for (int i = 3; i < copy.length - 3; i++) {
            float s1 = copy[i + 1];
            float s2 = copy[i + 2];
            float s3 = copy[i + 3];
            float s1Minus = copy[i - 1];
            float s2Minus = copy[i - 2];
            float s3Minus = copy[i - 3];
            reverseD[i] = ((22 * (s3Minus - s3) - 67 * (s2Minus - s2) - 58 * (s1Minus - s1)) / 252);
            if (tempIncrease) {
                reverseD[i] = -reverseD[i];
            }
        }

        float tempY = 0;
        //Y值最大的点（精确到0.2）
        int maxX = 0;
        for (int k = 0; k < reverseD.length; k++) {
            if (reverseD[k] > tempY) {
                tempY = reverseD[k];
                maxX = k;
            }
        }
        float finalX;

        WeightedObservedPoints points = new WeightedObservedPoints();
        points.add(maxX - 2, reverseD[maxX - 2]);
        points.add(maxX - 1, reverseD[maxX - 1]);
        points.add(maxX, reverseD[maxX]);
        points.add(maxX + 1, reverseD[maxX + 1]);
        points.add(maxX + 2, reverseD[maxX + 2]);

        PolynomialCurveFitter polyFitter = PolynomialCurveFitter.create(2);
        double[] params = polyFitter.fit(points.toList());

        if (params[2] > 0) {
            //二次项系数a>0表示开口向上
            Log.i(TAG, "曲线拟合失败");
            return;
        }
        //已知函数开口向下，获取最高点的值
        double x = -(params[1] / (2 * params[2]));
        finalX = new BigDecimal(x).setScale(2, BigDecimal.ROUND_HALF_UP).floatValue();

        if (tempIncrease) {
            finalX = startTemp + finalX * lightAndFrequency;
        } else {
            finalX = startTemp - lightAndFrequency - finalX * lightAndFrequency;
        }
        bean.setMeltFit(copy);
        bean.setMeltD(reverseD);
        bean.setMeltCtValue(finalX);
    }
}